package com.yvon.maple.cloud.web.handler;


import cn.hutool.core.text.CharSequenceUtil;
import com.yvon.boot.common.result.GlobalException;
import com.yvon.boot.common.result.ResponseData;
import com.yvon.boot.common.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.yvon.boot.common.result.ResultCode.GLOBAL_EXCEPTION;
import static com.yvon.boot.common.result.ResultCode.RUNTIME_EXCEPTION;

/**
 * 全局异常处理程序
 *
 * @author : Yvon
 * @since : 2021-09-15
 */
@Slf4j
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 数据校验异常处理.
     *
     * @param e the e
     * @return the response data
     * @author : Yvon / 2019-05-20
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.OK)
    public ResponseData bindException(BindException e) {
        FieldError fieldError = e.getFieldError();
        log.warn("数据校验异常信息 ex={}", fieldError.getDefaultMessage(), e);
        return ResponseData.failed(ResultCode.INVALID_PARAMETER.getCode(), fieldError.getDefaultMessage());
    }

    /**
     * 数据校验
     *
     * @param e the e
     * @return R r
     * @author : Yvon / 2019-04-24
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.OK)
    public ResponseData bodyValidExceptionHandler(MethodArgumentNotValidException e) {
        List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
        log.warn("数据校验异常信息 ex={}", fieldErrors.get(0).getDefaultMessage(), e);
        return ResponseData.failed(ResultCode.INVALID_PARAMETER.getCode(), fieldErrors.get(0).getDefaultMessage());
    }


    /**
     * Illegal argument exception handler response data.
     *
     * @param e the e
     * @return the response data
     * @author : Yvon / 2020-05-18
     */
    @ExceptionHandler({IllegalArgumentException.class})
    @ResponseStatus(HttpStatus.OK)
    public ResponseData illegalArgumentExceptionHandler(IllegalArgumentException e) {
        log.warn("非法参数 ex={}", e.getMessage(), e);
        return ResponseData.failed(ResultCode.ILLEGAL_ARGUMENT.getCode(), e.getMessage());
    }


    /**
     * Access denied exception response data.
     *
     * @param e the e
     * @return the response data
     * @author : Yvon / 2020-06-22
     */
    @ExceptionHandler({AccessDeniedException.class})
    @ResponseStatus(HttpStatus.OK)
    public ResponseData accessDeniedException(AccessDeniedException e) {
        log.warn("拒绝访问 ex={}", e.getMessage(), e);
        return ResponseData.failed(HttpStatus.FORBIDDEN.value(), e.getMessage());
    }

    /**
     * 文件大小超出异常处理.
     *
     * @param e the e
     * @return the response data
     * @author : Yvon / 2020-05-25
     */
    @ExceptionHandler({MaxUploadSizeExceededException.class})
    @ResponseStatus(HttpStatus.OK)
    public ResponseData maxUploadSizeExceededException(MaxUploadSizeExceededException e) {
        log.warn("文件大小超出最大限制", e);
        return ResponseData.failed(ResultCode.MAX_UPLOAD_SIZE_EXCEEDED);
    }


    /**
     * 请求方法不支持异常处理.
     *
     * @param e e
     * @return ResponseData
     * @author : Yvon / 2022-08-01
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public ResponseData requestMethodNotSupportedException(Exception e) {
        log.warn("HttpRequestMethodNotSupportedException异常信息 ex={}", e.getMessage(), e);
        return ResponseData.failed(ResultCode.REQUEST_METHOD_NOT_SUPPORTED);
    }


    /**
     * http媒体类型不支持
     *
     * @param e e
     * @return R<?>
     * @author : Yvon / 2022-08-01
     */
    @ExceptionHandler({HttpMediaTypeNotSupportedException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseData httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        MediaType contentType = e.getContentType();
        if (contentType != null) {
            String msg  = "请求类型(Content-Type)[" + contentType + "] 与实际接口的请求类型不匹配";
            log.warn("请求类型不匹配异常 ex={}", msg, e);
            return ResponseData.failed(ResultCode.MEDIA_TYPE_NOT_SUPPORTED.getCode(), msg);

        }else {
            log.warn("请求类型不匹配异常 ex={}", e.getMessage(), e);
            return ResponseData.failed(ResultCode.MEDIA_TYPE_NOT_SUPPORTED);
        }
    }

    /**
     * http消息不可读异常
     *
     * @param e e
     * @return ResponseData
     * @author : Yvon / 2022-08-01
     */
    @ExceptionHandler({HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseData httpMessageNotReadableException(HttpMessageNotReadableException e) {
        String message = e.getMessage();
        log.warn("HttpMessageNotReadableException ex={}", message, e);
        if (CharSequenceUtil.containsAny(message, "Could not read document:")) {
            String msg = String.format("无法正确的解析json类型的参数：%s", CharSequenceUtil.subBetween(message, "Could not read document:", " at "));
            return ResponseData.failed(ResultCode.PARAM_EX.getCode(), msg);
        } else {
            return ResponseData.failed(ResultCode.PARAM_EX);
        }
    }

    /**
     * 方法参数类型不匹配异常
     *
     * @param e e
     * @return ResponseData
     * @author : Yvon / 2022-08-01
     */
    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseData methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        String msg = "参数：[" + e.getName() + "]的传入值：[" + e.getValue() + "]与预期的字段类型：[" + Objects.requireNonNull(e.getRequiredType()).getName() + "]不匹配";
        log.warn("方法参数类型不匹配异常 ex={}", msg, e);
        return ResponseData.failed(ResultCode.PARAM_EX.getCode(),  msg);
    }


    /**
     * 唯一键冲突异常
     *
     * @param e e
     * @return ResponseData
     * @author : Yvon / 2022-08-01
     */
    @ExceptionHandler({DuplicateKeyException.class})
    @ResponseStatus(HttpStatus.OK)
    public ResponseData duplicateKeyException(DuplicateKeyException e) {
        log.error("唯一键冲突异常信息 ex={}", e.getMessage(), e);
        return ResponseData.failed(ResultCode.DUPLICATE_PRIMARY_KEY);
    }


    /**
     * 运行时异常处理.
     *
     * @param e the e
     * @return R r
     * @author : Yvon / 2019-04-24
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.OK)
    public ResponseData runtimeException(RuntimeException e) {
        log.error("运行时异常信息 ex={}", e.getMessage(), e);
        return ResponseData.failed(RUNTIME_EXCEPTION);
    }


    /**
     * 自定义全局异常.
     *
     * @param e the e
     * @return the response data
     * @author : Yvon / 2020-05-25
     */
    @ExceptionHandler({GlobalException.class})
    @ResponseStatus(HttpStatus.OK)
    public ResponseData globalExceptionHandler(GlobalException e) {
        log.error("自定义全局异常 ex={}", e.getMessage(), e);
        return ResponseData.failed(e.getCode(), e.getMessage());
    }

    /**
     * 全局异常处理.
     *
     * @param e the e
     * @return R r
     * @author : Yvon / 2019-04-24
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    public ResponseData exception(Exception e) {
        log.error("全局异常信息 ex={}", e.getMessage(), e);
        return ResponseData.failed(GLOBAL_EXCEPTION);
    }

    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResponseData missingServletRequestParameterException(HttpServletRequest request, MissingServletRequestParameterException ex) {
        String parameterName = ex.getParameterName();
        String message = "请求参数" + parameterName + "缺失!";
        log.error("校验异常 ex={}", message);
        return ResponseData.failed(org.apache.http.HttpStatus.SC_BAD_REQUEST, message);
    }

//    @ExceptionHandler(value = Exception.class)
//    public ResponseData unknownException(HttpServletRequest request, Exception ex) {
//        ExceptionLog(request, ex);
//        /*这里spring不支持直接拦截sql相关异常，要等进入拦截exception的拦截时，具体去判断属于哪种异常*/
//        if (ex.getCause() instanceof MysqlDataTruncation){
//            return ResponseData.error(org.apache.http.HttpStatus.SC_NOT_ACCEPTABLE, "数据入库失败，字段超长");
//        }
//        return ResponseData.error(org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR, "系统内部繁忙");
//    }


    private void exceptionLog(HttpServletRequest request, Exception ex) {
        exceptionLog(request, ex, false);
    }

    private void exceptionLog(HttpServletRequest request, Exception ex, boolean alarm) {
        String method = request.getMethod();
        String errorMsg;
        if (method.equals("GET")) {
            Map<String, String[]> map = request.getParameterMap();
            StringBuilder builder = new StringBuilder();
            map.forEach((k, v) -> builder.append("【key:").append(k).append("&").append("value:").append(Arrays.toString(v)).append("】"));
            errorMsg = "接口请求地址:" + request.getRequestURI() + ",请求方式：" + method + "\n 请求参数：\n" + builder + "\n触发了异常：\n" + ex;
            log.error(errorMsg);
        } else {
            StringBuilder content = new StringBuilder();
            try {
                ServletInputStream ris = request.getInputStream();
                byte[] b = new byte[1024];
                int lens = -1;
                while ((lens = ris.read(b)) > 0) {
                    content.append(new String(b, 0, lens));
                }
            } catch (IOException e) {
                log.error("堆栈跟踪日志", e);
            }
            errorMsg = "接口请求地址:" + request.getRequestURI() + ",请求方式：" + method + "\n 请求参数：\n" + content + "\n触发了异常：\n" + ex;
            log.error(errorMsg);
        }
        log.error("堆栈跟踪日志:", ex);
//        if (alarm && dingErrorHelper != null) {
//            dingErrorHelper.sendDingDingMsg("\n应用：" + application + "\nhost：" + IPUtils.getHostname() + "\nip：" + request.getLocalAddr() + "\n" + errorMsg);
//        }
    }
}
